//==============================================================================
//
//              DFile - the file class in the ofc-library
//
//             Copyright (C) 2003-2005  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2005-02-05 06:25:37 $ $Revision: 1.18 $
//
//==============================================================================

#include "ofc/DFile.h"

#ifdef WIN32
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#endif

#include <errno.h>
#include <unistd.h>

#include "ofc/DDatable.h"


#if _INTERFACE_

#include <stdio.h>

#include "ofc/config.h"

#include "ofc/DText.h"
#include "ofc/DData.h"
#include "ofc/DList.h"
#include "ofc/DTextReadable.h"
#include "ofc/DTextWritable.h"
#include "ofc/DDataReadable.h"
#include "ofc/DDataWritable.h"
#include "ofc/DDateTime.h"

#define DFILE_SEEK_SET  0
#define DFILE_SEEK_CUR  1
#define DFILE_SEEK_END  2

// The DFile class implements a number of methods for opening of, writing to, 
// reading from and closing of files.

@interface DFile : Object <DTextReadable,DTextWritable,DDataReadable,DDataWritable>
{
@private
  FILE           *_file;    // the file object
}

#endif



@implementation DFile


//// Constructors

//
// Initialise an empty file object
//
// @return the object
//

- (DFile *) init
{
  [super init];

  _file  = NULL;
  
  return self;
}

//
// Open a file
//
// @param name     the filename (c-string)
// @param mode     the file mode (c-string) (r,r+,w,w+,a,a+)
//
// @return the object
//

- (DFile *) init :(const char *) name :(const char *) mode
{
  [self init];

  [self open :name :mode];
  
  return self;
}


//// Deconstructor

//
// Free the object
//
// @return the file object
//

- free
{
  if (_file != NULL)
  {
    [self close];
  }
  
  [super free];

  return self;
}


//// Copy related methods

//
// Deepen a copied object (not implemented)
//
// @return the object
//

- deepen
{
  [super deepen];

  WARNING(DW_METHOD_NOT_IMPL, "deepen");

  return self;
}


//// File info methods

//
// Check if the file is open
//
// @return is the file open ?
//

- (BOOL) isOpen
{
  return (_file != NULL);
}

//
// Return the last error
//
// @return the last error
//

- (int) error
{
  return errno;
}

//
// Check if the file is an interactive terminal
//
// @return is it ?
//
 
- (BOOL) isAtty
{
  int  filedes  = [self fileno];

  if (filedes != -1)
  {
    return isatty(filedes);
  }
  else
  {
    return NO;
  }
}

//
// Return the file descriptor 
//
// @return the file descriptor (or -1 on error)
//

- (int) fileno
{
  return (_file != NULL) ? fileno(_file) : -1;
}


//// File opening methods

//
// Open a file
//
// @param name     the filename
// @param mode     the file mode (r,r+,w,w+,a,a+)
//
// @return is the file open ?
//

- (BOOL) open :(const char *) name :(const char *) mode
{
  if (_file != NULL)
  {
    [self close];
  }
  
  _file = fopen(name, mode);

  return (_file != NULL);
}


//// TextReadable protocol implementation

//
// Read a text string (all available text)
//
// @return a (new) text string
//

- (DText *) readText
{
  DText *string = [DText alloc];

  [string init];

  if (_file != NULL)
  {
    const
    int   length = 2048;
    char  buffer[length];
    char *result;
     
    while (!feof(_file))
    {
      result = fgets(buffer, length, _file);
      
      if (result != NULL)
      {
        [string append :buffer];
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
    
  return string;
}

//
// Read a text string 
//
// @param length   the length of the text to be read
//
// @return a (new) text string
//

- (DText *) readText :(long) length
{
  DText *string = [DText alloc];

  [string init];

  if ((_file != NULL) && (length > 0))
  {
    const int      size = 2048;
    unsigned char  buffer[size];
    unsigned char *result;
    long           total = 0;
    long           part  = size;
    
    while ((!feof(_file)) && (total < size))
    {
      if ((length - total) < part)
      {
        part = length - total - 1;
      }

      result = fgets(buffer, part, _file);

      if (result != NULL)
      {
        [string append :buffer];

        total += strlen(buffer);
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return string;
}  

//
// Read a line of text (till Eof or \n)
//
// @return a (new) text string (or nil for no data)
//

- (DText *) readLine;
{
  DText *string = nil;
  
  if (_file != NULL)
  {
    int ch = fgetc(_file);

    if (ch != EOF)
    {
      string = [DText alloc];

      [string init];

      while ((ch != '\n') && (ch != EOF))
      {
        [string push :ch];

        ch = fgetc(_file);
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
  
  return string;
}

//
// Read a character
//
// @return the character read (EOS for EOF)
//

- (char) readChar;
{
  char ch = EOS;

  if (_file != NULL)
  {
    ch = fgetc(_file);

    if (ch == EOF) ch = EOS;
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return ch;
}

//
// Tell the current position in the readable
//
// @return the current position (or -1 for error)
//

- (unsigned long) tell;
{
  return (_file != NULL) ? ftell(_file) : -1;
}

//
// Move the current position in the readable
//
// @param offset   the offset from the origin (in bytes)
// @param origin   the origin for the offset (0=begin, 1=current, 2=end)
//
// @return success
//

- (BOOL) seek :(unsigned long) offset :(int) origin;
{
  return (_file != NULL) ? (fseek(_file, offset, origin) != -1) : NO;
}

//
// Skip a number of positions
//
// @param offset   the number of bytes to skip
//
// @return success
//

- (BOOL) skip :(unsigned long) offset;
{
  return (_file != NULL) ? (fseek(_file, offset, SEEK_SET) != -1) : NO; 
}

//
// Check if the end of the file is reached
//
// @return is it?
//

- (BOOL) isEof;
{
  return (_file != NULL) ? (feof(_file)) : YES;
}


//// Implementation of DTextWritable

//
// Write text string 
//
// @param text     the text to be written
//
// @return success
//

- (BOOL) writeText :(const char *) text;
{
  return (_file != NULL) ? (fputs(text, _file) != EOF) : NO;
}

//
// Write line (appending a '\n')
//
// @param text     the text to be written
//
// @return success
//

- (BOOL) writeLine :(const char *) text
{
  BOOL ok = (_file != NULL);
  
  if (ok)
  {
    ok &= (fputs(text, _file) != EOF);
    ok &= (fputc('\n', _file) != EOF);
  }

  return ok;
}

//
// Write a character
//
// @param ch       the character to be written
//
// @return success
//

- (BOOL) writeChar :(char) ch;
{
  return (_file != NULL) ? (fputc(ch, _file) != EOF) : NO;
}


//// Methods for DDataReadable protocol

//
// Read a data string 
//
// @param length   the length of the data
//
// @return the object
//

- (DData *) readData :(unsigned long) length;
{
  DData *string = [DData alloc];

  [string init];
  if ((_file != NULL) && (length > 0))
  {
    const int     size  = 2048;
    unsigned char buffer[size];
    unsigned long total = 0;
    unsigned long part  = size;
    unsigned long result;
    
    while ((!feof(_file)) && (total < length))
    {
      if ((length - total) < part)
      {
        part = length - total;
      }
      
      result = fread(buffer, sizeof(buffer[0]), part, _file);
      
      if (result > 0)
      {
        [string append :buffer :result];

        total += result;
      }
    }
  }

  return string;
}

//
// Read a byte
//
// @return the byte read (0 for error)
//

- (unsigned char) readByte;
{
  unsigned char ch = 0;

  if (_file != NULL)
  {
    if (fread(&ch, sizeof(ch), 1, _file) < 1)
    {
      ch = 0;
    }
  }

  return ch;
}

//
// Read a short
//
// @return the short
//

- (short) readShort;
{
  short nr = 0;

  if (_file != NULL)
  {
    if (fread(&nr, sizeof(nr), 1, _file) < 1)
    {
      nr = 0;
    }
  }

  return nr;
}

//
// Read a long
//
// @return the long
//

- (long) readLong;
{
  long nr = 0;

  if (_file != NULL)
  {
    if (fread(&nr, sizeof(nr), 1, _file) < 1)
    {
      nr = 0;
    }
  }

  return nr;
}

//
// Read a double
//
// @return the double
//

- (double) readDouble;
{
  double nr = 0.0;

  if (_file != NULL)
  {
    if (fread(&nr, sizeof(nr), 1, _file) < 1)
    {
      nr = 0.0;
    }
  }

  return nr;
}


//// DDataWritable protocol implementation

//
// Write a data string 
//
// @param data     the data to be written
// @param length   the length of the data
//
// @return success
//

- (BOOL) writeData :(const unsigned char *) text :(unsigned long) length;
{
  BOOL ok = NO;

  if (_file != NULL)
  {
    ok = (fwrite(text, sizeof(unsigned char), length, _file) == length);
  }

  return ok;
}
  
//
// Write a byte
//
// @param byte     the byte to be written
//
// @return success
//

- (BOOL) writeByte :(unsigned char) byte;
{
  BOOL ok = NO;

  if (_file != NULL)
  {
    ok = (fwrite(&byte, sizeof(unsigned char), 1, _file) == 1);
  }

  return ok;
}
 
//
// Write a short 
//
// @param nr       the short to be written
//
// @return success
//

- (BOOL) writeShort :(short) nr;
{
  BOOL ok = NO;

  if (_file != NULL)
  {
    ok = (fwrite(&nr, sizeof(nr), 1, _file) == 1);
  }

  return ok;
}
 
//
// Write a long
//
// @param nr       the long to be written
//
// @return success
//

- (BOOL) writeLong :(long) nr
{
  BOOL ok = NO;

  if (_file != NULL)
  {
    ok = (fwrite(&nr, sizeof(nr), 1, _file) == 1);
  }

  return ok;
}
 
//
// Write a double
//
// @param nr       the double to be written
//
// @return success
//

- (BOOL) writeDouble :(double) nr
{
  BOOL ok = NO;

  if (_file != NULL)
  {
    ok = (fwrite(&nr, sizeof(nr), 1, _file) == 1);
  }
  return ok;
}


//// List related methods

//
// Read all lines from a file into a list of strings
//
// @return a (new) list of (new) DText objects
//

- (DList *) readLines
{
  DList   *list = [DList alloc];
 
  [list init];
 
  if (_file != NULL)
  {
    [self seek :0 :0];
    while (!feof(_file))
    {
      DText *line = [self readLine];

      if (line != nil)
      {
        [list append :line];
      }
    }
  }

  return list;
}

//
// Writes all the elements ('lines') of text from a list to file
//
// @param list     the list with objects that responds to textable protocol
//
// @return success
//

- (BOOL) writeLines :(DList *) list
{
  BOOL success = YES;
  
  if (list != NULL)
  {
    DListIterator *iter  = [DListIterator alloc];
    id             object;

    [iter init :list];

    object = [iter first];            
    while ((object != nil) && (success))
    {
      if ([object conformsTo :@protocol(DTextable)])
      {
        DText *string = [object toText];
  
        success = [self writeLine :[string cstring]];

        [string free];
      }
      
      object = [iter next];
    }
    
    [iter free];
  }
  
  return success;
}


//// File manipulation

//
// Truncate the file (not implemented for WIN32)
//
// @param size     the location of the truncation (-1 = current file position)
//
// @return success
//

- (BOOL) truncate :(long) size
{
  BOOL success = NO;
  int  filedes = [self fileno];

  if (filedes != -1)
  {
    size = (size < 0) ? [self tell] : size;

#ifndef WIN32
    success = (ftruncate(filedes, size) != -1);
#endif 
  }
  
  return success;
}

//
// Flush the output buffers of the file
//
// @return success
//

- (BOOL) flush
{
  return (_file != NULL) ? (fflush(_file) != -1) : NO;
}


//// File closing methods

//
// Close the file
//
// @return the file object
//

- close
{
  if (_file != NULL)
  {
    fclose(_file);

    _file = NULL;
  }

  return self;
}


//// Class methods

#if _PRIVATE_
static int _error = 0;
#endif

//
// Return the last error for the class methods
// 
// @return the last error
// 

+ (int) error
{
  return _error;
}

//
// Move/Rename a file
// 
// @param path         the current path
// @param newPath      the new path
// 
// @return success
// 

+ (BOOL) move :(const char *) path :(const char *) newPath
{
  BOOL ok = NO;
  
  if ((path == NULL) || (*path == EOS))
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else if ((newPath == NULL) || (*path == EOS))
  {
    WARNING(DW_INVALID_ARG, "newPath");
  }
  else
  {
#ifdef WIN32
    ok = MoveFile(path, newPath);
    
    if (!ok)
    {
      _error = GetLastError();
    }
#else
    ok = (rename(path, newPath) == 0);
    
    if (!ok)
    {
      _error = errno;
    }
#endif
  }
  
  return ok;
}

// 
// Remove a file
// 
// @param path         the path 
// 
// @return success
// 

+ (BOOL) remove :(const char *) path
{
  BOOL ok = NO;
  
  if ((path == NULL) || (*path == EOS))
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else
  {
#ifdef WIN32
    ok = DeleteFile(path);
    
    if (!ok)
    {
      _error = GetLastError();
    }
#else
    ok = (unlink(path) == 0);
    
    if (!ok)
    {
      _error = errno;
    }
#endif
  }
  
  return ok;
}

//
// Check if the file exists (as file)
// 
// @param path         the path of the file
// 
// @return does it ?
// 

+ (BOOL) isFile :(const char *) path
{
  BOOL ok = NO;
  
  if ((path == NULL) || (*path == EOS))
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else
  {
#ifdef WIN32
    DWORD attributes = GetFileAttributes(path);
    
    if (attributes != INVALID_FILE_ATTRIBUTES)
    {
      ok = ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
    }
    else
    {
      _error = GetLastError();
    }
#else
    struct stat info;
    
    ok = (stat(path, &info) == 0);
    
    if (ok)
    {
      ok = !S_ISDIR(info.st_mode);
    }
    else
    {
      _error = errno;
    }
#endif
  }
  
  return ok;
}

//
// Check if the file exists as directory
// 
// @param path         the path of the file/directory
// 
// @return does it ?
// 

+ (BOOL) isDirectory :(const char *) path
{
  BOOL ok = NO;
  
  if ((path == NULL) || (*path == EOS))
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else
  {
#ifdef WIN32
    DWORD attributes = GetFileAttributes(path);
    
    if (attributes != INVALID_FILE_ATTRIBUTES)
    {
      ok = ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0);
    }
    else
    {
      _error = GetLastError();
    }
#else
    struct stat info;
    
    ok = (stat(path, &info) == 0);
    
    if (ok)
    {
      ok = S_ISDIR(info.st_mode);
    }
    else
    {
      _error = errno;
    }
#endif
  }
  
  return ok;
}

//
// Return the file size
// 
// @param path         the path of the file
// 
// @return the size in bytes of the file (or -1 for error)
//

+ (long long) size :(const char *) path
{
  long long size = -1;
  
  if ((path == NULL) || (*path == EOS))
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else
  {
#ifdef WIN32
    WIN32_FIND_DATA info;
    HANDLE          handle;

    handle = FindFirstFile(path, &info);
    
    if (handle != INVALID_HANDLE_VALUE)
    {
      size = info.nFileSizeHigh * (MAXDWORD+1) + info.nFileSizeLow;
      
      FindClose(handle);
    }
#else
    struct stat info;
    BOOL        ok;
    
    ok = (stat(path, &info) == 0);
    
    if (ok)
    {
      size = info.st_size;
    }
#endif
  }
  
  return size;
}

//
// Return the last modified time of a file
// 
// @param path         the path of the file
// 
// @return a (new) DDateTime object with the last modification date/time (or nil for error)
// 
+ (DDateTime *) modified :(const char *) path
{
  DDateTime *modified = nil;
  
  if ((path == NULL) || (*path == EOS))
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else
  {
#ifdef WIN32
    WIN32_FIND_DATA info;
    HANDLE          handle;

    handle = FindFirstFile(path, &info);
    
    if (handle != INVALID_HANDLE_VALUE)
    {
      SYSTEMTIME st;
      
      if (FileTimeToSystemTime(&info.ftLastWriteTime, &st))
      {
        modified = [DDateTime new];
        
        [modified set :st.wYear :st.wMonth :st.wDay :st.wHour :st.wMinute :st.wSecond :st.wMilliseconds];
      }
      
      FindClose(handle);
    }
#else
    struct stat info;
    BOOL        ok;
    
    ok = (stat(path, &info) == 0);
    
    if (ok)
    {
      struct tm *mtime;
      
      mtime = localtime(&info.st_mtime);
      
      modified = [DDateTime new];
      
      [modified set :mtime->tm_year+1900 :mtime->tm_mon+1 :mtime->tm_mday :mtime->tm_hour :mtime->tm_min :mtime->tm_sec :0];
    }
#endif
  }
  
  return modified;
}

//
// Return the last accessed time of a file
// 
// @param path         the path of the file
// 
// @return a (new) DDateTime object with the last accessed date/time (or nil for error)
// 
+ (DDateTime *) accessed :(const char *) path
{
  DDateTime *accessed = nil;
  
  if ((path == NULL) || (*path == EOS))
  {
    WARNING(DW_INVALID_ARG, "path");
  }
  else
  {
#ifdef WIN32
    WIN32_FIND_DATA info;
    HANDLE          handle;

    handle = FindFirstFile(path, &info);
    
    if (handle != INVALID_HANDLE_VALUE)
    {
      SYSTEMTIME st;
      
      if (FileTimeToSystemTime(&info.ftLastAccessTime, &st))
      {
        accessed = [DDateTime new];
        
        [accessed set :st.wYear :st.wMonth :st.wDay :st.wHour :st.wMinute :st.wSecond :st.wMilliseconds];
      }
      
      FindClose(handle);
    }
#else
    struct stat info;
    BOOL        ok;
    
    ok = (stat(path, &info) == 0);
    
    if (ok)
    {
      struct tm *mtime;
      
      mtime = localtime(&info.st_atime);
      
      accessed = [DDateTime new];
      
      [accessed set :mtime->tm_year+1900 :mtime->tm_mon+1 :mtime->tm_mday :mtime->tm_hour :mtime->tm_min :mtime->tm_sec :0];
    }
#endif
  }
  
  return accessed;
}

@end

/*===========================================================================*/

